package strutil

import (
	"math/rand"
	"product/common/validator"
	"reflect"
	"regexp"
	"strings"
	"time"
	"unicode"
	"unicode/utf8"
)

// rng is a random number generator.
var rng = rand.New(rand.NewSource(time.Now().UnixNano()))

// CamelCase converts a string to camelCase.
// It capitalizes the first letter of each word and concatenates them.
// For example, "hello world" becomes "HelloWorld".
func CamelCase(strValue string) string {
	var builder strings.Builder
	strArray := splitInfoStrings(strValue, false)
	for i, str := range strArray {
		if i > 0 {
			builder.WriteString(strings.ToLower(str))
			continue
		}
		builder.WriteString(Capitalize(str))
	}
	return builder.String()
}

// Capitalize capitalizes the first letter of a string.
// For example, "hello" becomes "Hello".
// It returns an empty string if the input string is empty.
func Capitalize(str string) string {
	result := make([]rune, len(str))
	for i, v := range str {
		if i == 0 {
			result[i] = unicode.ToUpper(v)
			continue
		}
		result[i] = v
	}
	return string(result)
}

// UpperFirst capitalizes the first letter of a string.
// For example, "hello" becomes "Hello".
func UpperFirst(strValue string) string {
	if len(strValue) == 0 {
		return ""
	}
	r, size := utf8.DecodeRuneInString(strValue)
	r = unicode.ToUpper(r)
	return string(r) + strValue[size:]
}

// LowerFirst converts the first letter of a string to lowercase.
// For example, "Hello" becomes "hello".
func LowerFirst(strValue string) string {
	if validator.IsEmptyString(strValue) {
		return ""
	}
	r, size := utf8.DecodeRuneInString(strValue)
	r = unicode.ToLower(r)
	return string(r) + strValue[size:]
}

// Padding pads a string with another string to a specified length.
// It returns the padded string.
// source: The string to pad.
// size: The desired length of the padded string.
// padStr: The string to pad with.
// Returns: The padded string.
func Padding(source string, size int, padStr string) string {
	return padAtPosition(source, size, padStr, 0)
}

// PaddingStart pads a string with another string to a specified length, starting from the beginning.
// It returns the padded string.
// source: The string to pad.
// size: The desired length of the padded string.
// padStr: The string to pad with.
// Returns: The padded string.
func PaddingStart(source string, size int, padStr string) string {
	return padAtPosition(source, size, padStr, 1)
}

func PaddingEnd(source string, size int, paddingStr string) string {
	return padAtPosition(source, size, paddingStr, 2)
}

// KebabCase converts a string to kebab-case.
// It converts the string to lowercase and replaces spaces with hyphens.
// For example, "Hello World" becomes "hello-world".
// source: The string to convert.
// Returns: The kebab-case string.
func KebabCase(source string) string {
	result := splitInfoStrings(source, false)
	return strings.Join(result, "-")
}

// UpperKebabCase converts a string to UPPER-KEBAB-CASE.
// It converts the string to uppercase and replaces spaces with hyphens.
// For example, "Hello World" becomes "HELLO-KEBAB-CASE".
// source: The string to convert.
// Returns: The UPPER-KEBAB-CASE string.
func UpperKebabCase(source string) string {
	result := splitInfoStrings(source, true)
	return strings.Join(result, "-")
}

// SnakeCase converts a string to snake_case.
// It converts the string to lowercase and replaces spaces with underscores.
// For example, "Hello World" becomes "hello_world".
// source: The string to convert.
// Returns: The snake_case string.
func SnakeCase(source string) string {
	result := splitInfoStrings(source, false)
	return strings.Join(result, "_")
}

// UpperSnakeCase converts a string to UPPER_SNAKE_CASE.
// It converts the string to uppercase and replaces spaces with underscores.
// For example, "Hello World" becomes "HELLO_SNAKE_CASE".
// source: The string to convert.
// Returns: The UPPER_SNAKE_CASE string.
func UpperSnakeCase(source string) string {
	result := splitInfoStrings(source, true)
	return strings.Join(result, "_")
}

// SubstringByFront returns a substring of a string before a specified character.
// It returns the substring before the specified character.
// source: The string to search.
// findChar: The character to find.
// Returns: The substring before the specified character.
func SubstringByFront(strVal, findChar string) string {
	i := strings.Index(strVal, findChar)
	if strVal == "" || i == -1 || findChar == "" {
		return strVal
	}
	return strVal[0:i]
}

// SubstringByEnd returns a substring of a string after a specified character.
// It returns the substring after the specified character.
// source: The string to search.
// findChar: The character to find.
// Returns: The substring after the specified character.
func SubstringByEnd(strVal, findChar string) string {
	i := strings.LastIndex(strVal, findChar)
	if strVal == "" || i == -1 || findChar == "" {
		return strVal
	}
	return strVal[0:i]
}

// SubstringBackByFront returns a substring of a string after a specified character.
// It returns the substring after the specified character.
// source: The string to search.
// findChar: The character to find.
// Returns: The substring after the specified character.
func SubstringBackByFront(strVal, findChar string) string {
	i := strings.Index(strVal, findChar)
	if strVal == "" || i == -1 || findChar == "" {
		return strVal
	}
	return strVal[i:]
}

// SubstringBackByEnd returns a substring of a string before a specified character.
// It returns the substring before the specified character.
// source: The string to search.
// findChar: The character to find.
// Returns: The substring before the specified character.
func SubstringBackByEnd(strVal, findChar string) string {
	i := strings.LastIndex(strVal, findChar)
	if strVal == "" || i == -1 || findChar == "" {
		return strVal
	}
	return strVal[i:]
}

// IsString checks if a given value is a string.
// It returns true if the value is a string, otherwise false.
// v: The value to check.
// Returns: True if the value is a string, otherwise false.
func IsString(v any) bool {
	if v == nil {
		return false
	}
	return reflect.TypeOf(v).Kind() == reflect.String
}

// Reserve reverses the order of characters in a string.
// It returns the reversed string.
// strVal: The string to reverse.
// Returns: The reversed string.
func Reserve(strVal string) string {
	r := []rune(strVal)
	for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
		r[i], r[j] = r[j], r[i]
	}
	return string(r)
}

// Wrap wraps a string with another string.
// It returns the wrapped string.
// strVal: The string to wrap.
// wrapStr: The string to wrap with.
// Returns: The wrapped string.
func Wrap(strVal string, wrapStr string) string {
	if wrapStr == "" || strVal == "" {
		return strVal
	}
	var sb strings.Builder
	sb.WriteString(wrapStr)
	sb.WriteString(strVal)
	sb.WriteString(wrapStr)
	return sb.String()
}

// UnWrap removes a wrapping string from a string.
// It returns the unwrapped string.
// strVal: The string to unwrap.
// wrapStr: The string to unwrap with.
// Returns: The unwrapped string.
func UnWrap(strVal string, wrapStr string) string {
	if strVal == "" || wrapStr == "" {
		return strVal
	}

	firstIndex := strings.Index(strVal, wrapStr)
	lastIndex := strings.LastIndex(strVal, wrapStr)

	if firstIndex != 0 || lastIndex != len(strVal)-1 || lastIndex <= 0 {
		return strVal
	}

	if len(wrapStr) <= lastIndex {
		strVal = strVal[len(wrapStr):lastIndex]
	}
	return strVal
}

// SplitEx splits a string into a slice of strings using a separator.
// It returns a slice of strings.
// strVal: The string to split.
// sep: The separator to use.
// removeEmptyString: Whether to remove empty
func SplitEx(strVal, sep string, removeEmptyString bool) []string {
	if sep == "" {
		return []string{}
	}

	n := strings.Count(strVal, sep) + 1
	a := make([]string, n)
	n--
	i := 0
	sepSave := 0
	ignore := false

	for i < n {
		m := strings.Index(strVal, sep)
		if m < 0 {
			break
		}
		ignore = false
		if removeEmptyString {
			if strVal[:m+sepSave] == "" || strVal[:m+sepSave] == " " {
				ignore = true
			}
		}
		if !ignore {
			a[i] = strVal[:m+sepSave]
			strVal = strVal[m+len(sep):]
			i++
		} else {
			strVal = strVal[m+len(sep):]
		}
	}

	var ret []string
	if !removeEmptyString {
		a[i] = strVal
		ret = a[:i+1]
		return ret
	}
	if !validator.IsEmptyString(strVal) {
		a[i] = strVal
		ret = a[:i+1]
		return ret
	}
	ret = a[:i]
	return ret
}

// Substrings returns a substring of a string.
// It returns a substring of a string.
// strVal: The string to search.
// offset: The offset to start from.
// length: The length of the substring.
// Returns: The substring.
func Substrings(strVal string, offset int, length uint) string {
	rs := []rune(strVal)
	size := len(rs)

	if offset < 0 {
		offset = size + offset
		if offset < 0 {
			offset = 0
		}
	}
	if offset > size {
		return ""
	}

	if length > uint(size)-uint(offset) {
		length = uint(size - offset)
	}

	str := string(rs[offset : offset+int(length)])

	return strings.Replace(str, "\x00", "", -1)
}

// Concat concatenates strings.
// It returns the concatenated string.
// str: The strings to concatenate.
// Returns: The concatenated string.
func Concat(length int, str ...string) string {
	if len(str) <= 0 {
		return ""
	}

	sb := strings.Builder{}
	if length <= 0 {
		sb.Grow(len(str[0]) * len(str))
	} else {
		sb.Grow(length)
	}

	for _, s := range str {
		sb.WriteString(s)
	}
	return sb.String()
}

// TemplateReplace replaces template variables in a string with values from a map.
// It returns the string with template variables replaced.
// template: The template string.
// data: A map of template variable names and their values.
// Returns: The string with template variables replaced.
func TemplateReplace(template string, data map[string]string) string {
	re := regexp.MustCompile(`\{(\w+)}`)

	result := re.ReplaceAllStringFunc(template, func(s string) string {
		key := strings.Trim(s, "{}")
		if val, ok := data[key]; ok {
			return val
		}

		return s
	})

	result = strings.ReplaceAll(result, "{{", "{")
	result = strings.ReplaceAll(result, "}}", "}")

	return result
}

// ExtractContent extracts content between two strings.
// It returns a slice of extracted content.
// s: The string to search.
// start: The start string.
// end: The end string.
// Returns: A slice of extracted content.
func ExtractContent(strVal, start, end string) []string {
	var result []string

	for {
		if _, after, ok := strings.Cut(strVal, start); ok {
			if before, _, ok := strings.Cut(after, end); ok {
				result = append(result, before)
				strVal = after
			} else {
				break
			}
		} else {
			break
		}
	}

	return result
}

// Rotate rotates a string by a specified number of characters.
// It returns the rotated string.
// str: The string to rotate.
// shift: The number of characters to rotate.
// Returns: The rotated string.
func Rotate(str string, shift int) string {
	if shift == 0 {
		return str
	}

	runes := []rune(str)
	length := len(runes)
	if length == 0 {
		return str
	}

	shift = shift % length

	if shift < 0 {
		shift = length + shift
	}

	var sb strings.Builder
	sb.Grow(length)

	sb.WriteString(string(runes[length-shift:]))
	sb.WriteString(string(runes[:length-shift]))

	return sb.String()
}

// Shuffle shuffles the characters in a string.
// It returns the shuffled string.
// str: The string to shuffle.
// Returns: The shuffled string.
func Shuffle(str string) string {
	runes := []rune(str)

	for i := len(runes) - 1; i > 0; i-- {
		j := rng.Intn(i + 1)
		runes[i], runes[j] = runes[j], runes[i]
	}

	return string(runes)
}

var (
	whitespaceRegexMatcher     *regexp.Regexp = regexp.MustCompile(`\s`)
	mutlWhitespaceRegexMatcher *regexp.Regexp = regexp.MustCompile(`[[:space:]]{2,}|[\s\p{Zs}]{2,}`)
)

// RemoveWhiteSpace removes whitespace from a string.
// It returns the string without whitespace.
// str: The string to remove whitespace from.
// Returns: The string without whitespace.
// replaceAll: Whether to replace all whitespace with a single space.
func RemoveWhiteSpace(str string, replaceAll bool) string {
	if replaceAll && str != "" {
		return strings.Join(strings.Fields(str), "")
	} else if str != "" {
		str = mutlWhitespaceRegexMatcher.ReplaceAllString(str, " ")
		str = whitespaceRegexMatcher.ReplaceAllString(str, " ")
	}

	return strings.TrimSpace(str)
}

// ContainsAny checks if a string contains any of the specified substrings.
// It returns true if the string contains any of the specified substrings, otherwise false.
// str: The string to search.
// subStrs: The substrings to search for.

func ContainsAny(str string, subStrs []string) bool {
	for _, v := range subStrs {
		if !strings.Contains(str, v) {
			continue
		}
		return true
	}

	return false
}

// HideString hides part of the string by replacing it with a specified character.
// It returns the hidden string.
// origin: The original string.
// start: The start index of the string to hide.
// end: The end index of the string to hide.
// replaceChar: The character to replace the hidden part with.
// Returns: The hidden string.
func HideString(origin string, start, end int, replaceChar string) string {
	size := len(origin)

	if start > size-1 || start < 0 || end < 0 || start > end {
		return origin
	}

	if end > size {
		end = size
	}

	if replaceChar == "" {
		return origin
	}

	startStr := origin[0:start]
	endStr := origin[end:size]

	replaceSize := end - start
	replaceStr := strings.Repeat(replaceChar, replaceSize)

	return startStr + replaceStr + endStr
}
